/*
 * BookFiles.java
 *
 *  created: 1.9.2011
 *  charset: UTF-8
 *  license: MIT (X11) (See LICENSE file for full license)
 */
package cz.mp.k3bg.core;

import cz.mp.k3bg.Application;
import cz.mp.k3bg.log.LoggerManager;
import cz.mp.util.FileUtils;
import cz.mp.util.StringUtils;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Logger;
        

/**
 * Třída {@code BookFiles} obsahuje seznam všech souborů 
 * potřebných k vytvoření knihy.
 * 
 * @author Martin Pokorný
 * @version 0.1
 * @see Metadata
 */
public class BookFiles {

    private static final boolean DEBUG = false;
    private static final Logger logger =
            LoggerManager.getLogger(BookFiles.class, DEBUG);
    
    /** Výchozí jméno souboru s věnováním, citáty apod. Je {@value}. */
    public static final String DEFAULT_PERSONALNOTES_FILENAME = 
            Application.NAME_SHORT + "_" + "personal_notes.html";
    /** Výchozí jméno NCX souboru. Je {@value}. */
    public static final String DEFAULT_NCX_FILENAME = 
            Application.NAME_SHORT + "_" + "navigation.ncx";
    /** Výchozí jméno souboru s obsahem. Je {@value}. */
    public static final String DEFAULT_TOC_FILENAME = 
            Application.NAME_SHORT + "_" + "toc.html";
    /** Výchozí jméno souboru s titulem a krátkou tiráží. Je {@value}. */
    public static final String DEFAULT_COLOPHON_FILENAME = 
            Application.NAME_SHORT + "_" + "colophon.html";
    /** Výchozí jméno souboru se seznamem ilustrací. Je {@value}. */
    public static final String DEFAULT_LOI_FILENAME = 
            Application.NAME_SHORT + "_" + "loi.html";
    
    /** Výchozí jméno souboru se stylem. Je {@value}. */
    public static final String DEFAULT_STYLE_FILENAME = 
            Application.NAME_SHORT + "_" + "stylesheet.css";
    
    /** Je {@value}. */
    public static final String ID_TOC = "toc";
    
    /** Je {@value}. */
    public static final String ID_COLOPHON = "colophon";
    /** Je {@value}. */
    public static final String ID_PERSONALNOTES = "personalNotes";
    /** Je {@value}. */
    public static final String ID_LOI = "loi";
    
    /** Je {@value}. */
    public static final String ID_COVER = "cover";
    /** Je {@value}. */
    public static final String ID_NCX = "ncx";
    
    /** Je {@value}. */
    public static final String ID_BOOK_BASE = "book";

    /** Je {@value}. */
    public static final String ID_IMAGE_BASE = "img";
    /** Je {@value}. */
    public static final String ID_STYLE_BASE = "css";
    

    /** 
     * Povinný! */        
    private ItemFile ncx;
    /** 
     * Povinný! */        
    private ItemFile opf;
    private HtmlFile colophon;
    /** 
     * Povinný! */        
    private HtmlFile toc;
    private HtmlFile loi;
    private HtmlFile personalNotes;
    
    /** Styl pro  ncx, toc, colophon, loi. */
    private StyleFile baseStyle;
    
    /** 
     * Obrázek obálky knihy. 
     * <p>
     * Povinný!
     */    
    private ItemImage coverImage;
    
    /**
     * Seznam souborů s textem knihy.
     * Záleží na pořadí. Každý soubor může být pouze jednou.
     * Text knihy začíná na začátku prvního souboru.
     */
    private ArrayList<HtmlFile> srcFiles = new ArrayList<HtmlFile>();
    
    /**
     * Seznam obrázků, které se používají ve všech zadaných HTML souborech.
     */
    private Set<ItemImage> images = new TreeSet<ItemImage>();
    
    /**
     * Seznam stylů, které se používají ve všech zadaných HTML souborech.
     */
    private Set<StyleFile> styleFiles = new TreeSet<StyleFile>();
    
    // (ve skutečnosti se nepoužije)
    public static final String DEFAULT_WORKING_DIR = "";
//            Application.CFG_DIR
//            + File.separator + "gen_" 
//            + DateUtils.getCurrentDate("yyyyMMdd_HHmmss");
    
    /**
     * Pracovní adresář.
     */
    private String workingDirPath = DEFAULT_WORKING_DIR;
    
        
    // -----
    
    /** */
    BookFiles() {
        logger.fine("");
    }
    
    /**
     * 
     * @param workingDirPath  pracovní adresář; odtud se berou zdrojové soubory 
     *      a ukládají se tam vygenerované soubory
     */
    public BookFiles(String workingDirPath) {
        logger.fine("");        
        setWorkingDirPathImpl(workingDirPath);
    }
    
    // -----
    
    
    /**
     * Získá množinu všech obrázků, na které vede odkaz z některého zdrojového
     * (HTML) souboru.
     * 
     * @return 
     */
    public Set<ItemImage> getImages() {
        images = new TreeSet<ItemImage>();
        images.addAll(getImages(toc));
        images.addAll(getImages(colophon));
        images.addAll(getImages(loi));
        images.addAll(getImages(personalNotes));
        for (HtmlFile htmlFile : srcFiles) {
            images.addAll(getImages(htmlFile));
        }
        createImageIds();
        return Collections.unmodifiableSet(images);
    }
    
    /**
     * Všem obrázkům v {@code images} nastaví ID.
     */
    private void createImageIds() {
        int i = 1;
        for (ItemImage img : images) {
            String baseName = FileUtils.getFileBaseName(img.getPath())
                    .replaceAll("\\s", "_");
            img.setId(ID_IMAGE_BASE + "_" + i + "_" + baseName);
            i++;
        }        
    }
    
    /**
     * Získá množinu obrázků zadaného zdrojového (HTML) souboru.
     * 
     * @param html
     * @return  množinu obrázků nebo prázdnou množinu
     */
    private Set<ItemImage> getImages(HtmlFile html) {
        if (html != null && html.isInitialized()) {
            return html.getImages();
        }
        else {
            return Collections.emptySet();
        }
    }
    
    /**
     * Získá množinu všech stylů, na které vede odkaz z některého zdrojového
     * (HTML) souboru.
     * 
     * @return 
     */
    public Set<StyleFile> getStyleFiles() {
        styleFiles = new TreeSet<StyleFile>();
        styleFiles.addAll(getStyleFiles(toc));
        styleFiles.addAll(getStyleFiles(colophon));
        styleFiles.addAll(getStyleFiles(loi));
        styleFiles.addAll(getStyleFiles(personalNotes));
        for (HtmlFile htmlFile : srcFiles) {
            styleFiles.addAll(getStyleFiles(htmlFile));
        }       
        createStylesIds();
        return Collections.unmodifiableSet(styleFiles);
    }
    
    /**
     * Všem stylům v {@code styles} nastaví ID.
     */
    private void createStylesIds() {
        int i = 1;
        for (ItemFile itemFile : styleFiles) {
            itemFile.setId(ID_STYLE_BASE + "_" + i);
            i++;
        }       
    }    
    
    /**
     * Získá množinu stylů zadaného zdrojového (HTML) souboru.
     * 
     * @param html
     * @return  množinu stylů nebo prázdnou množinu
     */
    private Set<StyleFile> getStyleFiles(HtmlFile html) {
        if (html != null && html.isInitialized()) {
            return html.getStyleFiles();
        }
        else {
            return Collections.emptySet();
        }
    }


    /**
     * 
     * @return 
     */
    public HtmlFile getColophon() {
        return colophon;
    }

    /**
     * Nastaví soubor obsahující jméno knihy, autora a krátkou tiráž.
     * Pokud je zadaný soubor {@code null}, žádný soubor se pro to nepoužije.
     * 
     * @param colophon
     * @throws IOException 
     */
    public void setColophon(HtmlFile colophon) throws IOException {
        logger.config("" + colophon);
        this.colophon = colophon;
        if (colophon == null) {
            return;
        }
        colophon.setId(ID_COLOPHON);
        
        try {
            colophon.initExisting();
        } catch (IOException ex) {
            logger.warning(ex.toString());
            throw ex;
        }
    }
    
    /**
     * 
     * @param colophonRelPath  může být relativní cesta v pracovním adresáři 
     *      ({@code workingDirPath}) nebo absolutní cesta k souboru.
     * @throws IOException 
     */
    public void setColophon(String colophonRelPath) throws IOException {
        setColophon(getHtmlFileFromRelPath(colophonRelPath));
    }
    
    /**
     * 
     * @throws IOException 
     */
    public void setDefaultColophon() throws IOException {
        setColophon(BookFiles.DEFAULT_COLOPHON_FILENAME);
    }
    
    
    /**
     * 
     * @return 
     */
    public HtmlFile getLoi() {
        return loi;
    }

    /**
     * Nastaví soubor obsahující jméno seznam ilustrací.
     * Pokud je zadaný soubor {@code null}, žádný soubor se pro to nepoužije.
     * 
     * @param loi
     * @throws IOException 
     */
    public void setLoi(HtmlFile loi) throws IOException {
        logger.config("" + loi);
        this.loi = loi;
        if (loi == null) {
            return;
        }
        loi.setId(ID_LOI);
         
        try {
            loi.initExisting();
        } catch (IOException ex) {
            logger.warning(ex.toString());
            throw ex;
        }
    }

    /**
     * 
     * @param loiRelPath  může být relativní cesta v pracovním adresáři 
     *      ({@code workingDirPath}) nebo absolutní cesta k souboru.
     * @throws IOException 
     */
    public void setLoi(String loiRelPath) throws IOException {
        setLoi(getHtmlFileFromRelPath(loiRelPath));
    }
    
    /**
     * 
     * @throws IOException 
     */
    public void setDefaultLoi() throws IOException {
        setLoi(BookFiles.DEFAULT_LOI_FILENAME);
    }
    
    
    /**
     * 
     * @return 
     */
    public HtmlFile getPersonalNotes() {
        return personalNotes;
    }

    /**
     * Nastaví soubor obsahující osobní poznámky a vzkazy jako je věnování,
     * citáty, básně.
     * Pokud je zadaný soubor {@code null}, žádný soubor se pro to nepoužije.
     * 
     * @param personalNotes
     * @throws IOException 
     */
    public void setPersonalNotes(HtmlFile personalNotes) throws IOException {
        logger.config("" + personalNotes);
        this.personalNotes = personalNotes;
        if (personalNotes == null) {
            return;
        }
        personalNotes.setId(ID_PERSONALNOTES);
        
        try {
            personalNotes.initExisting();
        } catch (IOException ex) {
            logger.warning(ex.toString());
            throw ex;
        }
    }

    /**
     *
     * @param notesRelPath  může být relativní cesta v pracovním adresáři 
     *      ({@code workingDirPath}) nebo absolutní cesta k souboru.
     * @throws IOException
     */
    public void setPersonalNotes(String notesRelPath) throws IOException {
        setPersonalNotes(getHtmlFileFromRelPath(notesRelPath));
    }

    /**
     * 
     * @throws IOException 
     */
    public void setDefaultPersonalNotes() throws IOException {
        HtmlFile defaultPersonalNotes = new HtmlFile(
                workingDirPath + File.separator + 
                BookFiles.DEFAULT_PERSONALNOTES_FILENAME);
        setPersonalNotes(defaultPersonalNotes);        
    }    
    
    
    /**
     * 
     * @return 
     */
    public ItemFile getNcx() {
        return ncx;
    }

    /**
     * Nastaví soubor NCX (navigace v obsahu).
     * 
     * @param ncx 
     */
    public void setNcx(ItemFile ncx) {
        logger.config("" + ncx);
        this.ncx = ncx;
        if (ncx == null) {
            return;
        }         
        ncx.setId(ID_NCX);
        ncx.setMimeType(MimeType.NCX);
    }
    
    /**
     * 
     * @throws IOException 
     */
    public void setDefaultNcx() throws IOException {
        ItemFile defaultNcx = new ItemFile(
                workingDirPath + File.separator + 
                BookFiles.DEFAULT_NCX_FILENAME);
        setNcx(defaultNcx);        
    }     
    
    
    /**
     * 
     * @return 
     */
    public ItemFile getOpf() {
        return opf;
    }

    /**
     * Nastaví soubor OPF (Open Packaging Format 2).
     * 
     * @param opf  nesmí být {@code null}
     * @throws IllegalArgumentException
     */
    public void setOpf(ItemFile opf) {
        logger.config("" + opf);
        if (opf == null) {
            logger.warning("opf = null");
            throw new IllegalArgumentException("opf = null");
        }
        this.opf = opf;
        opf.setMimeType(MimeType.OPF);
    }
    
    /**
     * 
     * @throws IOException 
     */
    public void setDefaultOpf(Metadata metadata) throws IOException {
        ItemFile defaultOpf = new ItemFile(
                workingDirPath + File.separator + Application.NAME_SHORT + "_" 
                + metadata.createStdBaseFileName() + ".opf");
        setOpf(defaultOpf);
    }  
    
    
    /**
     * 
     * @return 
     */
    public HtmlFile getToc() {       
        return toc;
    }

    /**
     * Nastaví soubor obsahující obsah.
     * 
     * @param toc  nesmí být {@code null}
     * @throws IOException 
     * @throws IllegalArgumentException
     */
    public void setToc(HtmlFile toc) throws IOException {
        logger.config("" + toc);
        if (toc == null) {
            logger.warning("toc = null");
            throw new IllegalArgumentException("toc = null");
        }        
        this.toc = toc;
        
        toc.setId(ID_TOC);
        
        try {
            toc.initExisting();
        } catch (IOException ex) {
            logger.warning(ex.toString());
            throw ex;
        }        
    }
    
    /**
     * 
     * @param tocRelPath
     * @throws IOException 
     */
    public void setToc(String tocRelPath) throws IOException {
        setToc(getHtmlFileFromRelPath(tocRelPath));
    }
    
    /**
     * 
     * @param relPath
     * @return 
     */
    private HtmlFile getHtmlFileFromRelPath(String relPath) {
        HtmlFile htmlFile = null;
        if (! StringUtils.isBlank(relPath)) {
            htmlFile = new HtmlFile(
                    workingDirPath + File.separator + relPath);
        }
        return htmlFile;
    }

    /**
     * 
     * @throws IOException 
     */
    public void setDefaultToc() throws IOException {
        setToc(BookFiles.DEFAULT_TOC_FILENAME);        
    }
       
    /**
     * Přidá zdrojový soubor knihy na konec knihy.
     * 
     * @param src
     * @throws IOException 
     */
    public void addSrcHtmlFile(HtmlFile src) throws IOException {
        if (src == null) {
            logger.warning("src = null");
            throw new IllegalArgumentException("src = null");
        }
        // HtmlFile v srcFiles  musí být jednoznačné; (a záleží na pořadí)
        if (this.srcFiles.contains(src)) {  
            logger.fine("" + src + "  SKIP");
            return;
        }
        logger.config("" + src);        
        this.srcFiles.add(src);
        src.setId(ID_BOOK_BASE + "_" + this.srcFiles.size());
        
        try {
            src.initExisting();
        } catch (IOException ex) {
            logger.warning(ex.toString());
            throw ex;
        }        
    }
    
    /**
     * 
     * @param srcFilePath  může být relativní cesta v pracovním adresáři 
     *      ({@code workingDirPath}) nebo absolutní cesta k souboru.
     * @throws IOException 
     */
    public void addSrcFile(String srcFilePath) throws IOException {
        HtmlFile htmlFile = null;
        if (new File(srcFilePath).isAbsolute()) {
            htmlFile = new HtmlFile(srcFilePath);
        }
        else {
            htmlFile = new HtmlFile(
                    workingDirPath + File.separator + srcFilePath);
        }
        addSrcHtmlFile(htmlFile);
    }
    
    /**
     * 
     * @param index
     * @return 
     */
    public HtmlFile getSrcHtmlFile(int index) {
        return this.srcFiles.get(index);
    }
    
    /**
     * 
     * 
     * @return 
     */
    public int getSrcFilesCount() {
        return this.srcFiles.size();
    }
    
    /**
     * 
     * @return 
     */
    public ArrayList<HtmlFile> getSrcHtmlFiles() {
        return new ArrayList<HtmlFile>(this.srcFiles);  // mělká kopie
    }    
    
    /**
     * Přidá celý seznam souborů představujících části knihy.
     * 
     * @param srcFiles  může být relativní cesta v pracovním adresáři 
     *      ({@code workingDirPath}) nebo absolutní cesta k souboru.
     * @throws IOException 
     */
    public void addSrcFiles(ArrayList<String> srcFiles) throws IOException {
        addSrcFiles ((String[]) srcFiles.toArray(new String[0]));      
    }    
    
    /**
     * 
     * @param srcFiles
     * @throws IOException 
     */
    public void addSrcFiles(String... srcFiles) throws IOException {
        if (srcFiles == null) {
            logger.warning("srcFiles = null");
            throw new IllegalArgumentException("srcFiles = null");
        }        
        logger.fine("");
        for (String srcFile : srcFiles) {
            addSrcFile(srcFile);
        } 
    }
    
    /**
     * Přidá celý seznam souborů představujících části knihy.
     * 
     * @param srcFiles
     * @throws IOException 
     */
    public void addSrcHtmlFiles(ArrayList<HtmlFile> srcFiles) throws IOException {
        if (srcFiles == null) {
            logger.warning("srcFiles = null");
            throw new IllegalArgumentException("srcFiles = null");
        }        
        logger.fine("");
        for (HtmlFile htmlFile : srcFiles) {
            addSrcHtmlFile(htmlFile);
        }        
    } 
    
    
    /**
     * 
     * @return 
     */
    public void clearSrcFiles() {
        this.srcFiles = new ArrayList<HtmlFile>();
    }  
    
    /**
     * Nastaví soubor s titulním obrázkem knihy.
     * 
     * @param cover 
     */
    public void setCoverImage(ItemImage cover) {
        logger.config("" + cover);
        if (cover == null) {
            logger.warning("cover = null");
            throw new IllegalArgumentException("cover=null");
        }          
        this.coverImage = cover;
        this.coverImage.setId(ID_COVER);
    }

    /**
     * 
     * @param coverRelPath 
     */
    public void setCoverImage(String coverRelPath) {
        ItemImage cover = null;
        if (! StringUtils.isBlank(coverRelPath)) {
            cover = new ItemImage(
                    workingDirPath + File.separator + coverRelPath);
        }
        setCoverImage(cover);
    }
    
    /**
     * 
     * @return 
     */
    public ItemImage getCoverImage() {
        return coverImage;
    }
    
    
    /**
     * 
     * @return 
     */
    public StyleFile getBaseStyle() {
        if (this.baseStyle == null) {
            setBaseStyle(new StyleFile(DEFAULT_STYLE_FILENAME));
        }
        return baseStyle;
    }

    /**
     * 
     * @param baseStyle 
     */
    public void setBaseStyle(StyleFile baseStyle) {
        logger.config("baseStyle = " + baseStyle);
        this.baseStyle = baseStyle;
        if (baseStyle != null) {
            this.styleFiles.add(baseStyle);
        }
    }
    
    /**
     * 
     * @throws IOException 
     */
    public void setDefaultBaseStyle() throws IOException {
        StyleFile defaultBaseStyle = new StyleFile(
                workingDirPath + File.separator + 
                BookFiles.DEFAULT_STYLE_FILENAME);
        setBaseStyle(defaultBaseStyle);        
    }    
    
    /**
     * 
     * @return 
     */
    public String getWorkingDirPath() {
        return workingDirPath;
    }

    /**
     * 
     * @param workingDirPath 
     */
    public void setWorkingDirPath(String workingDirPath) {
        setWorkingDirPathImpl(workingDirPath);
    }
    
    /**
     * (pracoví adresář - jako absolutní cesta)
     * 
     * @param workingDirPath 
     */
    private void setWorkingDirPathImpl(String workingDirPath) {
        if (workingDirPath == null) {
            throw new IllegalArgumentException("workingDirPath = null");
        }
        if (workingDirPath.trim().isEmpty()) {
            this.workingDirPath = "";
            logger.config("workingDirPath = ''");
        }
        else {
            this.workingDirPath = FileUtils.getAbsoluteFilePath(workingDirPath);
            logger.config("workingDirPath = " + workingDirPath);
        }
    }

}   // BookFiles.java
